#!/usr/bin/perl -w
package HtmlReport;
use warnings;
use strict;
require 5.007;
use File::Basename;
use POSIX;
my $module_path = dirname($0);
use SimpleError;

use constant FILL_NA => '<em>(n/a)</em>';
use constant VALUE_NA => 'n/a';

use constant DIAGRAM_WIDTH => 200;
use constant DISTRIB_ID_STRING => 'distrib';
use constant YILED_ID_STRING => 'distrib';

## forward declarations
sub htmlize($);

##
## C O N F I G U R A T I O N S
##
my %config = (
    'Html_Template' => 'template.html',
    'Css_Source'    => 'template.css',
    'Css_Style'     => 'embedded',
);

##
## Subroutines (instance methods)
##
sub new {
    my $class = shift;
    my $self = +{ @_ };
    bless $self, $class;
}

sub set {
    my $self = shift;
    %{$self} = (%$self, @_);
}

sub set_data {
    my $self = shift;
    my ($wafer_data, $analysis_data) = @_;
    if (! defined($wafer_data)) {
        error("Wafer data is mandatory!");
    }
    $self->{Wafer_Data} = $wafer_data;
    $self->{Analysis_Data} = $analysis_data;
    return $self;
}

sub output {
    my $self = shift;
    error("Wafer data not set!") unless (defined($self->{Wafer_Data}));
    warning("Stats data not set!") unless (defined($self->{Analysis_Data}));
    ## read HTML source
    my $html = read_file($config{Html_Template});
    my $fill = $self->collect_data();
    ## fill in the data!
    $html =~ s@
        %%([[:alpha:]]\w*)%%
        @
        defined($fill->{$1}) ? $fill->{$1} : FILL_NA()
    @exg;
    return $html;
}

sub collect_data {
    my $self = shift;
    my %fill;
    $fill{CSS} = $config{Css_Style} eq 'import' ?
        "\@import url($config{Css_Source});" : read_file($config{Css_Source});
    my $generate_time = get_timestamp();
    my $cp_duration   = $self->{Wafer_Data}{CP_Seconds};
    my $device_name   = $self->{Wafer_Data}{Device_Name};
    my $lot_number    = $self->{Wafer_Data}{Lot_Number};
    my $wafer_number  = sprintf("%02d", $self->{Wafer_Data}{Wafer_Number});
    my $wafer_id;
    if ($lot_number =~ /^(\d[[:alpha:]]{3}\d{5})(\d)-(\d{2})$/) {
        $lot_number = "$1.$2";
        $wafer_number = $3;
        $wafer_id = "$lot_number-$wafer_number";
    } else {
        $wafer_id = $lot_number;
    }
    ## legacy code
    ## module LogParse has added this routine, so remove it in the safe future...
    if ($device_name eq VALUE_NA() && $self->{Wafer_Data}{Test_Program_Name} =~ /(hm\d+)/i) {
        $device_name = uc($1);
    }
    ## summary data
    $fill{TITLE}           = "$device_name $wafer_id CP Result";
    $fill{DEVICE_NAME}     = $device_name;
    $fill{WAFER_ID}        = $wafer_id;
    $fill{GENERATE_TIME}   = $generate_time;
    $fill{LOT_NUMBER}      = $lot_number;
    $fill{WAFER_NUMBER}    = $wafer_number;
    $fill{GROSS_DIES}      = $self->{Wafer_Data}{Gross_Dies};
    $fill{PROBING_TIMES}   = $self->{Wafer_Data}{Parse_Dies};
    $fill{CP_START}        = $self->{Wafer_Data}{CP_Start};
    $fill{CP_END}          = $self->{Wafer_Data}{CP_End};
    $fill{CP_DURATION}     = sprintf("%dmin %dsec", int($cp_duration / 60), $cp_duration % 60);
    $fill{CP_TIME_PER_DIE} = sprintf("%.3fms", $self->{Wafer_Data}{MilliS_Per_Die});
    $fill{TEST_PROGRAM}    = $self->{Wafer_Data}{Test_Program_Name};
    $fill{LOG_FILE}        = $self->{Wafer_Data}{Log_File};
    # misc data
    $fill{NOTES}           = defined($self->{Notes}) ? htmlize($self->{Notes}) : FILL_NA();
    $fill{WAFER_LIST}      = $self->other_wafer_list();
    $fill{YEAR}            = (localtime())[5] + 1900;
    ## detailed data
    if (defined($self->{Analysis_Data}{Stats_Data})) {
        ($fill{TOC_DISTRIBUTION}, $fill{CONTENT_DISTRIBUTION})
            = render_distribution($self->{Analysis_Data}{Stats_Data}, $fill{GROSS_DIES});
    }
    if (defined($self->{Analysis_Data}{Yield_Data})) {
        #($fill{TOC_YILED}, $fill{CONTENT_YIELD}) = render_yield($self->{Yield_Data});
    }
    ## done
    return \%fill;
}

sub other_wafer_list {
    my $self = shift;
    my $wafer_nos = $self->{Wafer_List};
    my $this_file = $self->{Output_File};
    my $this_no   = $self->{Wafer_Data}{Wafer_Number};
    my $file_pattern = $this_file;
    if ($file_pattern =~ s/^(\w{9}\.?[\d]{1,2}-?)([\d]{2})(.*)$/${1}%02d${3}/) {
        message("output file pattern: ${1}%WAFER_NO%${3}");
    } else {
        warning("cannot deduce output file name patter to generate wafer list");
        warning("a default pattern will be used");
        ## legacy code! future wafer data should always contain the 'Lot_ID' key
        if (defined($self->{Wafer_Data}{Lot_ID})) {
            $file_pattern = $self->{Wafer_Data}{Lot_ID};
            $file_pattern =~ s/\.//;
            $file_pattern .= '%02d.report.html';
        } else {
            ($file_pattern = $self->{Wafer_Data}{Lot_Number}) =~ s/-..$/%02d.report.html/;
        }
    }
    if (defined($wafer_nos) && @$wafer_nos > 0) { # other wafers exist
        my @list_sources;
        for my $no (@$wafer_nos) {
            push @list_sources,
                sprintf "\t\t\t\t<a href=\"$file_pattern\"%s>%02d</a>",
                $no, $no == $this_no ? ' class="this"' : '', $no;
        }
        return "\t\t\t<p>\n" . join(" |\n", @list_sources) . "\n\t\t\t</p>";
    } else {
        warning("but no other wafer list specified!");
        return FILL_NA();
    }
}

##
## class methods
##

sub render_yield {
    my ($data, $gross_dies) = @_;
    my @item_sources;
    my @toc_sources;
    # enclsoing tags
    unshift @toc_sources, <<"EOF";
			<ol>
EOF
    push @toc_sources, <<"EOF";
			</ol>
EOF
    return (
        join("", @toc_sources),
        join("", @item_sources)
    );
}

sub render_distribution {
    my ($data, $gross_dies) = @_;
    my @item_sources;
    my @toc_sources;
    for my $i (0 .. $#{$data}) {
        my $item_data = $data->[$i];
        my $item_name = $item_data->{Item};
        message("creating distribution diagram for: $item_name");
        my $id_string = sprintf("%s%03d", DISTRIB_ID_STRING(), $i);
        push @item_sources, item_distribution_source($item_data, $gross_dies, $i);
        push @toc_sources, <<"EOF";
				<li><a href="#$id_string">$item_name</a></li>
EOF
    }
    # enclsoing tags
    unshift @toc_sources, <<"EOF";
			<ol>
EOF
    push @toc_sources, <<"EOF";
			</ol>
EOF
    return (
        join("", @toc_sources),
        join("", @item_sources)
    );
}

sub item_distribution_source {
    my ($item_data, $gross_dies, $no) = @_;
    ## heading
    my $id_string = sprintf("%s%03d", DISTRIB_ID_STRING(), $no);
    my $heading = <<EOF;
<h3 class="item" id="$id_string"><a href="#$id_string">$item_data->{Item}</a></h3>
<div class="item-navi">
	Jump to&nbsp;&raquo;&raquo;<br />
	<ul>
		<li><a href="#masthead">Top of page</a></li>
		<li>-</li>
		<li>
			<a href="#toc">Table of Contents</a><br />
			<ul>
				<li><a href="#toc-yield">Yields</a></li>
				<li><a href="#toc-distribution">Distributions</a></li>
			</ul>
		</li>
		<li>-</li>
		<li><a href="#footer">Bottom of page</a></li>
	</ul>
</div>
EOF
    if (defined($item_data->{Desc})) {
        $heading .= <<"EOF";
	<p class="item-desc">
		$item_data->{Desc}
	</p>
EOF
    }

    ## summary table
    my $sample_dies = $item_data->{Stats}{Sample_Dies};
    my $valid_dies  = $item_data->{Stats}{Valid_Dies};
    my $median      = $item_data->{Stats}{Median};
    my $mean        = $item_data->{Stats}{Mean};
    my $stdev       = $item_data->{Stats}{Stdev};
    my $sample_dies_fill = sprintf("%d", $sample_dies, $sample_dies * 100 / $gross_dies);
    my $valid_dies_fill  = sprintf("%d", $valid_dies, $valid_dies * 100 / $gross_dies);
    my $median_fill      = $median eq VALUE_NA() ? VALUE_NA() : sprintf("%.3f", $median);
    my $mean_fill        = $mean   eq VALUE_NA() ? VALUE_NA() : sprintf("%.3f", $mean);
    my $stdev_fill       = $stdev  eq VALUE_NA() ? VALUE_NA() : sprintf("%.3f", $stdev);
    my $summary_table    = <<"EOF";
	<table class="stats">
		<tr>
			<th>Gross Dies</th>
			<th>Samples</th>
			<th>Valid</th>
			<th>Median</th>
			<th>Mean</th>
			<th>StDev</th>
		</tr>
		<tr>
			<td>$gross_dies</td>
			<td>$sample_dies_fill</td>
			<td>$valid_dies_fill</td>
			<td>$median_fill</td>
			<td>$mean_fill</td>
			<td>$stdev_fill</td>
		</tr>
	</table>
EOF

    ## diagram table
    my ($limit_desc, $limit_count) = (
        $item_data->{Stats}{Distribution}[0],
        $item_data->{Stats}{Distribution}[1],
    );
    my $diagram_table_heading = <<"EOF";
	<tr>
		<th class="no">#</th>
		<th class="limit">Limit</th>
		<th class="value">Count</th>
		<th class="diagram">Diagram</th>
		<th class="percent">Percent</th>
	</tr>
EOF
    my @source_rows;
    for my $i (0 .. $#{$limit_desc}) {
        my $desc  = htmlize($limit_desc->[$i]);
        my $count = $limit_count->[$i];
        my $percent = $count / $gross_dies;
        my $diag_width = int($percent * DIAGRAM_WIDTH());
        $percent = $percent * 100;
        push @source_rows, sprintf(<<"EOF", $i + 1, $desc, $count, $diag_width, $percent);
	<tr>
		<td class="no">%d</td>
		<td class="limit">%s</td>
		<td class="value">%d</td>
		<td class="diagram"><div class="diag-cont"><div class="diagram" style="width: %dpx"></div></div></td>
		<td class="percent">%.2f%%</td>
	</tr>
EOF
    }
    my $diagram_type = sprintf("type%d", 1 + $no % 3);
    return sprintf(<<"EOF", $heading, $summary_table, $diagram_table_heading, join("", @source_rows));
<!-- heading -->
%s
<!-- summary table -->
%s
	<table class="distrib $diagram_type">
	<!-- diagram table heading -->
%s
	<!-- diagram table body -->
%s
	</table>
<!-- END OF DISTRIBUTION DIAGRAM TABLES -->

EOF
}

sub read_file {
    my ($filename) = @_;
    my $path = $module_path =~ m/^(?:[[:alpha:]]:)?[\\\/]/o ?
        "$module_path/$filename" : $filename;
    open READ_FILE_INPUT, '<', $path or error("cannot read file: $path: $!");
    local $/;
    my $content = <READ_FILE_INPUT>;
    close READ_FILE_INPUT;
    return $content;
}

sub htmlize($) {
    my ($str) = @_;
    $str =~ s/\&/\&amp;/g;
    $str =~ s/</\&lt;/g;
    $str =~ s/>/\&gt;/g;
    return $str;
}

sub get_timestamp {
    my $now = time();
    my $timezone = strftime("%z", localtime($now));
    return scalar(localtime($now)) . " $timezone";
}

1;
